import java.util.concurrent.PriorityBlockingQueue;

class MyTask implements Comparable<MyTask> {
    public Runnable runnable;
    public long time;

    // 构造方法 描述一个任务
    public MyTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        // 当前时刻的时间戳 加上 等待时间，作为该任务开始执行的时间戳
        this.time = System.currentTimeMillis() + delay;
    }

    // 实现Comparable，必须是可以比较的才能放到优先级队列里
    @Override
    public int compareTo(MyTask o) {
        return (int)(this.time - o.time);
    }
}

public class MyTimer {
    // 优先级阻塞队列
    private PriorityBlockingQueue<MyTask> priorityBlockingQueue = new PriorityBlockingQueue();

    // 创建一个锁对象
    private Object locker = new Object();

    // 安排任务的方法
    public void schedule(Runnable runnable, long delay) {
        MyTask myTask = new MyTask(runnable, delay);
        priorityBlockingQueue.put(myTask);
        synchronized (locker) {
            // 插入了新的任务，唤醒等待，需要比较新任务的优先级
            locker.notify();
        }
    }

    // 构造方法, 构造一个线程, 线程中执行安排的任务
    public MyTimer() {
        Thread thread = new Thread(() -> {
            while (true) {
                try {
                    // 这个锁如果只加到wait上可能会出问题
                    // 比如: 在队列中取出一个30分钟后的任务
                    // 这时队列新增了一个10分钟后的任务
                    // 因为锁只加到wait上了, 新任务入队后那里直接就notify了,但是现在还没有等待
                    // 然后进入if语句判断, 执行else, 将30分钟后的任务放回队列
                    // 再然后这个线程就开始等待30分钟, 这意味着新增的10分钟后的任务没有被感知到
                    // 等到wait超时结束, 再从队列中取出任务(就是那个新任务), 这个任务已经超时20分钟了
                    //
                    // 所以要这个锁要包含取出任务在内
                    // 这样如果取出任务后, 又新增了任务, 会等到这个线程wait时, 才notify唤醒
                    synchronized (locker) {
                        // 从优先级阻塞队列中取出一个任务
                        MyTask myTask = priorityBlockingQueue.take();
                        // 当前时间戳
                        long curTime = System.currentTimeMillis();
                        if (curTime >= myTask.time) {
                            // 当前时间戳大于等于任务开始的时间戳
                            // 执行任务
                            myTask.runnable.run();
                        } else {
                            // 时间没到，放回队列
                            priorityBlockingQueue.put(myTask);
                            // 等待，防止忙等（如果不 wait 会一直插入，取出队列）
                            locker.wait(myTask.time - curTime);
                            // 时间到了就结束等待
                            // 如果有新任务插入队列，也会结束等待
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
    }

    public static void main(String[] args) {
        MyTimer myTimer = new MyTimer();
        myTimer.schedule(() -> {
            System.out.println("hello5");
        }, 5000);
        myTimer.schedule(() -> {
            System.out.println("hello4");
        }, 4000);
        myTimer.schedule(() -> {
            System.out.println("hello3");
        }, 3000);
        myTimer.schedule(() -> {
            System.out.println("hello2");
        }, 2000);
        myTimer.schedule(() -> {
            System.out.println("hello1");
        }, 1000);

        System.out.println("hello0");
    }
}
